# alot config loader is sensitive to leading space !
{ config, lib, pkgs, ... }:

with lib;

let

  cfg = config.programs.alot;

  enabledAccounts =
    filter (a: a.notmuch.enable) (attrValues config.accounts.email.accounts);

  # sorted: primary first
  alotAccounts = sort (a: b: !(a.primary -> b.primary)) enabledAccounts;

  boolStr = v: if v then "True" else "False";

  mkKeyValue = key: value:
    let value' = if isBool value then boolStr value else toString value;
    in "${key} = ${value'}";

  mk2ndLevelSectionName = name: "[" + name + "]";

  tagSubmodule = types.submodule {
    options = {
      translated = mkOption {
        type = types.nullOr types.str;
        description = ''
          Fixed string representation for this tag. The tag can be
          hidden from view, if the key translated is set to
          <literal>""</literal>, the empty string.
        '';
      };

      translation = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = ''
          A pair of strings that define a regular substitution to
          compute the string representation on the fly using
          <literal>re.sub</literal>.
        '';
      };

      normal = mkOption {
        type = types.nullOr types.str;
        default = null;
        example = "'','', 'white','light red', 'white','#d66'";
        description = ''
          How to display the tag when unfocused.
          See <link xlink:href="https://alot.readthedocs.io/en/latest/configuration/theming.html#tagstring-formatting"/>.
        '';
      };

      focus = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = "How to display the tag when focused.";
      };
    };
  };

  accountStr = account:
    with account;
    concatStringsSep "\n" ([ "[[${name}]]" ]
      ++ mapAttrsToList (n: v: n + "=" + v) ({
        address = address;
        realname = realName;
        sendmail_command =
          optionalString (alot.sendMailCommand != null) alot.sendMailCommand;
        sent_box = "maildir" + "://" + maildir.absPath + "/" + folders.sent;
        draft_box = "maildir" + "://" + maildir.absPath + "/" + folders.drafts;
      } // optionalAttrs (aliases != [ ]) {
        aliases = concatStringsSep "," aliases;
      } // optionalAttrs (gpg != null) {
        gpg_key = gpg.key;
        encrypt_by_default = if gpg.encryptByDefault then "all" else "none";
        sign_by_default = boolStr gpg.signByDefault;
      } // optionalAttrs (signature.showSignature != "none") {
        signature = pkgs.writeText "signature.txt" signature.text;
        signature_as_attachment = boolStr (signature.showSignature == "attach");
      }) ++ [ alot.extraConfig ] ++ [ "[[[abook]]]" ]
      ++ mapAttrsToList (n: v: n + "=" + v) alot.contactCompletion);

  configFile = let
    bindingsToStr = attrSet:
      concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${v}") attrSet);
  in ''
    # Generated by Home Manager.
    # See http://alot.readthedocs.io/en/latest/configuration/config_options.html

    ${generators.toKeyValue { inherit mkKeyValue; } cfg.settings}
    ${cfg.extraConfig}
    [tags]
  '' + (let
    submoduleToAttrs = m:
      filterAttrs (name: v: name != "_module" && v != null) m;
  in generators.toINI { mkSectionName = mk2ndLevelSectionName; }
  (mapAttrs (name: x: submoduleToAttrs x) cfg.tags)) + ''
    [bindings]
    ${bindingsToStr cfg.bindings.global}

    [[bufferlist]]
    ${bindingsToStr cfg.bindings.bufferlist}
    [[search]]
    ${bindingsToStr cfg.bindings.search}
    [[envelope]]
    ${bindingsToStr cfg.bindings.envelope}
    [[taglist]]
    ${bindingsToStr cfg.bindings.taglist}
    [[thread]]
    ${bindingsToStr cfg.bindings.thread}

    [accounts]

    ${concatStringsSep "\n\n" (map accountStr alotAccounts)}
  '';

in {
  options = {
    programs.alot = {
      enable = mkOption {
        type = types.bool;
        default = false;
        example = true;
        description = ''
          Whether to enable the Alot mail user agent. Alot uses the
          Notmuch email system and will therefore be automatically
          enabled for each email account that is managed by Notmuch.
        '';
      };

      hooks = mkOption {
        type = types.lines;
        default = "";
        description = ''
          Content of the hooks file.
        '';
      };

      bindings = mkOption {
        type = types.submodule {
          options = {
            global = mkOption {
              type = types.attrsOf types.str;
              default = { };
              description = "Global keybindings.";
            };

            bufferlist = mkOption {
              type = types.attrsOf types.str;
              default = { };
              description = "Bufferlist mode keybindings.";
            };

            search = mkOption {
              type = types.attrsOf types.str;
              default = { };
              description = "Search mode keybindings.";
            };

            envelope = mkOption {
              type = types.attrsOf types.str;
              default = { };
              description = "Envelope mode keybindings.";
            };

            taglist = mkOption {
              type = types.attrsOf types.str;
              default = { };
              description = "Taglist mode keybindings.";
            };

            thread = mkOption {
              type = types.attrsOf types.str;
              default = { };
              description = "Thread mode keybindings.";
            };
          };
        };
        default = { };
        description = ''
          Keybindings.
        '';
      };

      tags = mkOption {
        type = types.attrsOf tagSubmodule;
        default = { };
        description = "How to display the tags.";
      };

      settings = mkOption {
        type = with types;
          let primitive = either (either (either str int) bool) float;
          in attrsOf primitive;
        default = {
          initial_command = "search tag:inbox AND NOT tag:killed";
          auto_remove_unread = true;
          handle_mouse = true;
          prefer_plaintext = true;
        };
        example = literalExpression ''
          {
            auto_remove_unread = true;
            ask_subject = false;
            thread_indent_replies = 2;
          }
        '';
        description = ''
          Configuration options added to alot configuration file.
        '';
      };

      extraConfig = mkOption {
        type = types.lines;
        default = "";
        description = ''
          Extra lines added to alot configuration file.
        '';
      };
    };

    accounts.email.accounts = mkOption {
      type = with types; attrsOf (submodule (import ./alot-accounts.nix pkgs));
    };
  };

  config = mkIf cfg.enable {
    home.packages = [ pkgs.alot ];

    xdg.configFile."alot/config".text = configFile;

    xdg.configFile."alot/hooks.py" = mkIf (cfg.hooks != "") {
      text = ''
        # Generated by Home Manager.
      '' + cfg.hooks;
    };
  };
}
